#include "layerviewer.h"

#include "Document.h"
#include "GraphicsItemFactory.h"
#include "graphicslayersetdialog.h"
#include "LayerListWidget.h"

#include "LeIpc2581/Ipc2581.h"
#include "LeIpc2581/Component.h"
#include "LeIpc2581/Content.h"
#include "LeIpc2581/enumtranslator.h"
#include "LeIpc2581/Feature.h"

#include "LeGraphicsView/LeGraphicsItem.h"
#include "LeGraphicsView/LeGraphicsItemLayer.h"
#include "LeGraphicsView/LeGraphicsScene.h"
#include "LeGraphicsView/LeGraphicsView.h"
#include "LeGraphicsView/Solarized.h"

#include "ItemModels/ComponentListModel.h"
#include "ItemModels/FeatureSetListModel.h"
#include "ItemModels/HoleListModel.h"
#include "ItemModels/LayerListModel.h"
#include "ItemModels/LayerSetModel.h"
#include "ItemModels/LayerStackupModel.h"
#include "ItemModels/MultiColumnFilterProxyModel.h"
#include "ItemModels/NetListModel.h"
#include "ItemModels/PackageListModel.h"
#include "ItemModels/PadListModel.h"

#include "qtpropertybrowser/qttreepropertybrowser.h"
#include "PropertyBrowser/ItemModelPropertyBrowserAdaptor.h"

#include <QCheckBox>
#include <QDebug>
#include <QDockWidget>
#include <QElapsedTimer>
#include <QGraphicsPathItem>
#include <QGraphicsItemGroup>
#include <QGraphicsDropShadowEffect>
#include <QGridLayout>
#include <QHeaderView>
#include <QLineEdit>
#include <QMainWindow>
#include <QMenu>
#include <QSettings>
#include <QSortFilterProxyModel>
#include <QSplitter>
#include <QTableView>
#include <QToolBar>
#include <QTreeView>
#include <QVBoxLayout>

Q_DECLARE_METATYPE(Ipc2581b::Outline*)

LayerViewer::LayerViewer(QObject *parent):
    IViewer(parent),
    m_graphicsScene(new LeGraphicsScene(this)),
    m_graphicsView(new LeGraphicsView),
    m_graphicsItemFactory(new GraphicsItemFactory(this)),

    m_layerSetModel(new LayerSetModel(this)),
    m_layerSetView(new QTableView),
    m_layerSetWidget(new LayerListWidget),
    m_layerSetDockWidget(new QDockWidget),

    m_layerListView(new QTableView),
    m_layerListDockWidget(new QDockWidget),
    m_componentListView(new QTableView),
    m_componentListDockWidget(new QDockWidget),
    m_packageListView(new QTableView),
    m_packageListDockWidget(new QDockWidget),
    m_netListView(new QTableView),
    m_netListDockWidget(new QDockWidget),
    m_padListView(new QTableView),
    m_padListDockWidget(new QDockWidget),
    m_holeListView(new QTableView),
    m_holeListDockWidget(new QDockWidget),
    m_featureSetListView(new QTableView),
    m_featureSetListDockWidget(new QDockWidget),
    m_stackupView(new QTreeView),
    m_stackupDockWidget(new QDockWidget),

    m_propertyBrowser(new QtTreePropertyBrowser),
    m_propertyBrowserDockWidget(new QDockWidget)
{
    createActions();
}

QString LayerViewer::errorMessage() const
{
    if (m_document != nullptr)
        return m_document->errorString();
    return QString();
}

bool LayerViewer::openFile(const QString &fileName)
{
    m_document = new Document(this);
    if (!m_document->open(fileName))
        return false;

    m_componentListModel = m_document->componentListModel();
    m_holeListModel = m_document->holeListModel();
    m_netListModel = m_document->netListModel();
    m_packageListModel = m_document->packageListModel();
    m_padListModel = m_document->padListModel();
    m_layerListModel = m_document->layerListModel();
    m_featureSetListModel = m_document->featureSetListModel();
    m_stackupModel = m_document->layerStackModel();

    setupScenePalette();

    loadDesignDictionaries();
    loadStepProfile();
    loadLayerSet();
    loadLayerFeatures();

    setupGraphicsScene();
    setupGraphicsView();


    setupTableViewDockWidget("Layers", m_layerListView, m_layerListDockWidget);
    setupTableView(m_packageListView, m_packageListModel);
    setupTableViewDockWidget("Packages", m_packageListView, m_packageListDockWidget);
    setupTableView(m_layerListView, m_layerListModel);
    setupTableViewDockWidget("Components", m_componentListView, m_componentListDockWidget);
    setupTableView(m_componentListView, m_componentListModel);
    setupTableViewDockWidget("Nets", m_netListView, m_netListDockWidget);
    setupTableView(m_netListView, m_netListModel);
    setupTableViewDockWidget("Pads", m_padListView, m_padListDockWidget);
    setupTableView(m_padListView, m_padListModel);
    setupTableViewDockWidget("Holes", m_holeListView, m_holeListDockWidget);
    setupTableView(m_holeListView, m_holeListModel);
    setupTableViewDockWidget("Layer set", m_layerSetView, m_layerSetDockWidget);
    setupTableView(m_layerSetView, m_layerSetModel);
    setupTableViewDockWidget("Feature sets", m_featureSetListView, m_featureSetListDockWidget);
    setupTableView(m_featureSetListView, m_featureSetListModel);

    setupTreeView(m_stackupView, m_stackupModel);
    setupTreeViewDockWidget("Layer stack", m_stackupView, m_stackupDockWidget);

    m_propertyBrowser->setObjectName("PropertiesView");
    m_propertyBrowserDockWidget->setWindowTitle("Properties");
    m_propertyBrowserDockWidget->setObjectName("PropertiesDockWidget");
    m_propertyBrowserDockWidget->setWidget(m_propertyBrowser);

    return true;
}


void LayerViewer::activate(QMainWindow *mainWindow)
{
    connect(m_packageListView->selectionModel(), &QItemSelectionModel::currentChanged,
            [this](const QModelIndex &current, const QModelIndex &/*previous*/) {
        if (!current.isValid())
            return;
        QAbstractItemView *packageView = m_packageListView;
        QAbstractItemModel *packageModel = packageView->model();
        QModelIndex index = packageModel->index(current.row(),
                                                PackageListModel::NameColumn);
        const QString packageName = packageModel->data(index).toString();
        const QVariant data = packageModel->data(current, PackageListModel::ComponentListModelRole);
        if (!data.canConvert<QAbstractItemModel*>())
            return;
        QAbstractItemModel *componentModel = data.value<QAbstractItemModel*>();
        QTableView *componentView = new QTableView;
        setupTableViewDockWidget(QString("Components (%1)").arg(packageName),
                                 componentView, m_propertyBrowserDockWidget);
        setupTableView(componentView, componentModel);
        m_graphicsScene->foregroundLayer()->clear();
        connect(componentView->selectionModel(), &QItemSelectionModel::currentChanged,
                [this, packageView, componentView](const QModelIndex &current, const QModelIndex &/*previous*/) {

            if (!current.isValid())
            {
                m_graphicsScene->foregroundLayer()->clear();
                return;
            }

            // Component transform
            QModelIndex index = componentView->model()->index(current.row(), ComponentListModel::TransformColumn);
            QVariant data = componentView->model()->data(index, Qt::EditRole);
            QTransform xform = data.value<QTransform>();
            // component location
            index = componentView->model()->index(current.row(), ComponentListModel::LocationColumn);
            data = componentView->model()->data(index, Qt::EditRole);
            QPointF location = data.value<QPointF>();
            // package outline
            index = packageView->currentIndex();
            data = packageView->model()->data(index, PackageListModel::OutlineRole);
            const Ipc2581b::Outline* outline = data.value<Ipc2581b::Outline*>();
            // Place outline on scene's FG layer
            auto item = m_graphicsItemFactory->createItem(outline, GftTrace);
            item->setPos(m_graphicsItemFactory->mapFromIpc(location));
            item->setTransform(xform);
            m_graphicsScene->foregroundLayer()->clear();
            m_graphicsScene->foregroundLayer()->addToLayer(item);
            // Fit outline nicely in view
            QRectF rect = item->mapToScene(item->boundingRect()).boundingRect();
            QPointF center = rect.center();
            rect = QTransform::fromScale(1.15, 1.15).mapRect(rect);
            rect.moveCenter(center);
            m_graphicsView->fitInView(rect, Qt::KeepAspectRatio);
        });
    });

    // TODO: Add toolbar with commonly used view actions

    QList<QDockWidget*> leftDockWidgets;
    leftDockWidgets << m_packageListDockWidget
                    << m_componentListDockWidget
                    << m_layerSetDockWidget
                    << m_layerListDockWidget
                    << m_netListDockWidget
                    << m_padListDockWidget
                    << m_holeListDockWidget
                    << m_featureSetListDockWidget
                    << m_stackupDockWidget;
    QList<QDockWidget*> rightDockWidgets;
    rightDockWidgets << m_propertyBrowserDockWidget;

    for (auto dock: leftDockWidgets)
        mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dock);
    for (auto dock: rightDockWidgets)
        mainWindow->addDockWidget(Qt::RightDockWidgetArea, dock);

    for (int i =0; i < leftDockWidgets.count() - 1; i++)
        mainWindow->tabifyDockWidget(leftDockWidgets.at(i), leftDockWidgets.at(i+1));
    for (int i =0; i < rightDockWidgets.count() - 1; i++)
        mainWindow->tabifyDockWidget(leftDockWidgets.at(i), leftDockWidgets.at(i+1));

    QSettings settings;
    restoreItemViewState(m_packageListView, settings);
    restoreItemViewState(m_componentListView, settings);
    restoreItemViewState(m_layerSetView, settings);
    restoreItemViewState(m_layerListView, settings);
    restoreItemViewState(m_netListView, settings);
    restoreItemViewState(m_padListView, settings);
    restoreItemViewState(m_holeListView, settings);
    restoreItemViewState(m_featureSetListView, settings);
    restoreItemViewState(m_stackupView, settings);

//    for (auto dock: leftDockWidgets)
//        dock->setMaximumWidth(mainWindow->width()/4.0);
//    for (auto dock: rightDockWidgets)
//        dock->setMaximumWidth(mainWindow->width()/4.0);
}

void LayerViewer::desactivate(QMainWindow *mainWindow)
{
    QSettings settings;
    saveItemViewState(m_packageListView, settings);
    saveItemViewState(m_componentListView, settings);
    saveItemViewState(m_layerSetView, settings);
    saveItemViewState(m_layerListView, settings);
    saveItemViewState(m_netListView, settings);
    saveItemViewState(m_padListView, settings);
    saveItemViewState(m_holeListView, settings);
    saveItemViewState(m_featureSetListView, settings);
    saveItemViewState(m_stackupView, settings);

    settings.beginGroup(mainWindow->objectName());
    settings.setValue("state", mainWindow->saveState());
    settings.endGroup();

    mainWindow->removeDockWidget(m_propertyBrowserDockWidget);
    mainWindow->removeDockWidget(m_stackupDockWidget);
    mainWindow->removeDockWidget(m_featureSetListDockWidget);
    mainWindow->removeDockWidget(m_holeListDockWidget);
    mainWindow->removeDockWidget(m_padListDockWidget);
    mainWindow->removeDockWidget(m_netListDockWidget);
    mainWindow->removeDockWidget(m_layerSetDockWidget);
    mainWindow->removeDockWidget(m_layerListDockWidget);
    mainWindow->removeDockWidget(m_componentListDockWidget);
    mainWindow->removeDockWidget(m_packageListDockWidget);
}

//static bool _LayerViewer_isConductor(Ipc2581b::LayerFunction function)
//{
//    switch (function)
//    {
//        case Ipc2581b::LayerFunction::Conductor:
//        case Ipc2581b::LayerFunction::Plane:
//            return true;
//        default:
//            return false;
//    }
//}

static bool _LayerViewer_isDielectric(Ipc2581b::LayerFunction function)
{
    switch (function)
    {
        case Ipc2581b::LayerFunction::Dieladhv:
        case Ipc2581b::LayerFunction::Dielbase:
        case Ipc2581b::LayerFunction::Dielcore:
        case Ipc2581b::LayerFunction::Dielpreg:
            return true;
        default:
            return false;
    }
}

void LayerViewer::loadStepProfile()
{
    if (m_document->stepProfile() == nullptr)
        return;

    auto option = m_graphicsScene->backgroundLayer()->featureOption();
    option.setFeatureVisible(GftFiducial, true); // FIXME: Need GftProfile
    option.setFeatureBrush(GftFiducial, Solarized::background);
    auto item = m_graphicsItemFactory->createItem(m_document->stepProfile(), GftFiducial);
    item->setEnabled(false);
    item->setFlag(QGraphicsItem::ItemIsSelectable, false);
    item->setAcceptHoverEvents(false);
    m_graphicsScene->backgroundLayer()->setFeatureOption(option);
    m_graphicsScene->backgroundLayer()->addToLayer(item);
}

void LayerViewer::loadDesignDictionaries()
{
    m_graphicsItemFactory->loadDictionaries(m_document->content());
    m_graphicsScene->setUnitOfMeasure(m_graphicsItemFactory->unit());
    m_graphicsScene->setUnitScaleFactor(m_graphicsItemFactory->scaleFactor());
}

void LayerViewer::loadLayerSet()
{
    int accentIndex = 0;
    int LayerIndex = 0;
    for (const Ipc2581b::Layer *ipcLayer: m_layerListModel->layers())
    {
        bool enabled = true;
        QColor color = Solarized::accent(accentIndex++);
        if (_LayerViewer_isDielectric(ipcLayer->layerFunction))
        {
            color = Solarized::secondaryContent;
            enabled = false;
        }
        else if (ipcLayer->layerFunction == Ipc2581b::LayerFunction::Soldermask)
            color = Solarized::secondaryContent;
        else if (ipcLayer->layerFunction == Ipc2581b::LayerFunction::Pastemask)
            color = Solarized::primaryContent;
        else if (ipcLayer->layerFunction == Ipc2581b::LayerFunction::Silkscreen)
            color = Solarized::yellow.lighter().lighter();
        else if (ipcLayer->layerFunction == Ipc2581b::LayerFunction::Drill)
            color = Solarized::backgroundHighlight;
        auto graphicsLayer = new LeGraphicsItemLayer();
        auto featureOption = LeGraphicsFeatureOption();
        featureOption.setAllFeatureVisible(true);
        featureOption.setAllFeatureBrush(QBrush(color, Qt::SolidPattern));
        featureOption.setFeatureBrush(GftFill, QBrush(color, Qt::DiagCrossPattern));
        featureOption.setFeatureBrush(GftHole, QBrush(color, Qt::Dense6Pattern));
        graphicsLayer->setFeatureOption(featureOption);
        graphicsLayer->setVisible(false);
        m_layerSetModel->addLayer(LayerIndex++, enabled, ipcLayer, graphicsLayer);
    }
}

void LayerViewer::setupGraphicsScene()
{
    QElapsedTimer timer;
    timer.restart();

    m_graphicsScene->setLayers(m_layerSetModel->enabledGraphicsLayers());
    auto rect = m_graphicsScene->itemsBoundingRect();
    rect = QTransform::fromScale(10, 10).map(rect).boundingRect();
    m_graphicsScene->setSceneRect(rect);
    m_graphicsScene->enableToolTip(true);
}

void LayerViewer::setupGraphicsView()
{
    m_graphicsView->setScene(m_graphicsScene);
    m_graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    m_graphicsView->setRenderHint(QPainter::Antialiasing);
    m_graphicsView->fitInView(m_graphicsScene->itemsBoundingRect(), Qt::KeepAspectRatio);
    m_graphicsView->scale(0.8, 0.8);
    m_graphicsView->setInteractionMode(LeGraphicsView::MultipleSelectionMode);
}

void LayerViewer::loadLayerFeatures()
{
    for (int row = 0; row < m_layerListModel->rowCount(); row++)
    {

        const QString layerName = m_layerListModel->layer(row)->name;
        const int layerSequenceNumber = m_layerSetModel->layerSequenceNumber(layerName);
        auto *graphicsLayer = m_layerSetModel->graphicsLayer(layerName);
        if (graphicsLayer == nullptr)
        {
            qWarning() << QString("Layer '%1' does not exists").arg(layerName);
            continue;
        }
        for (const Ipc2581b::Set *set: m_layerListModel->featureSetList(row))
        {
            QMap<QString, QString> metaData;
            metaData.insert("Layer", QString("%1, %2").arg(layerSequenceNumber).arg(layerName));
            if (set->netOptional.hasValue)
                metaData.insert("Net", set->netOptional.value);
            if (set->polarityOptional.hasValue)
                metaData.insert("Polarity", Ipc2581b::EnumTranslator::polarityText(set->polarityOptional.value));
            if (set->padUsageOptional.hasValue)
                metaData.insert("Pad", Ipc2581b::EnumTranslator::padUsageText(set->padUsageOptional.value));
            if (set->testPointOptional.hasValue)
                metaData.insert("Test point", set->testPointOptional.value ? "Yes" : "No");
            if (set->geometryOptional.hasValue)
                metaData.insert("Geometry", set->geometryOptional.value);
            if (set->plateOptional.hasValue)
                metaData.insert("Plated", set->plateOptional.value ? "Yes" : "No");
            if (set->componentRefOptional.hasValue)
                metaData.insert("Component", set->componentRefOptional.value);

            // TODO: set->specRefList

            for (auto features: set->featuresList)
            {
                for (auto item: m_graphicsItemFactory->createItems(features))
                {
                    item->setFeatureMetaData(metaData);
                    graphicsLayer->addToLayer(item);
                }
            }

            // TODO: package pad vs via (PadUsage)
            // TODO: Pass metadata (layer, net, pad usage, test point)
            for (auto pad: set->padList)
            {
                auto item = m_graphicsItemFactory->createItem(pad);
                QString pinToolTipText;
                if (pad->pinRefOptional.hasValue)
                {
                    const Ipc2581b::PinRef *pinRef = pad->pinRefOptional.value;
                    if (pinRef->componentRefOptional.hasValue)
                        metaData.insert("Pin", QString("%1.%2").arg(pinRef->componentRefOptional.value).arg(pinRef->pin));
                    else
                        qWarning() << pad << ": Pad has a pin ref but no component ref";
                }
                if (!pad->padstackDefRef.isEmpty()) // TBD: XSD says not optional, pdf says optional
                    metaData.insert("Padstack", pad->padstackDefRef);
                else
                    qWarning() << pad << ": Pad has no padstack ref";
                item->setFeatureMetaData(metaData);
                graphicsLayer->addToLayer(item);
            }

            for (auto hole: set->holeList)
            {
                auto item = m_graphicsItemFactory->createItem(hole);
                metaData.insert("Hole", hole->name);
                metaData.insert("Diameter", QString::number(hole->diameter));
                item->setFeatureMetaData(metaData);
                graphicsLayer->addToLayer(item);
            }

            for (auto fiducial: set->fiducialList)
            {
                auto item = m_graphicsItemFactory->createItem(fiducial);
                item->setFeatureMetaData(metaData);
                graphicsLayer->addToLayer(item);
            }

            for (auto slot: set->slotCavityList)
            {
                auto item = m_graphicsItemFactory->createItem(slot);
                item->setFeatureMetaData(metaData);
                graphicsLayer->addToLayer(item);
            }
        }
    }
}

void LayerViewer::setupScenePalette()
{
    LeGraphicsPalette palette;
    palette.setBrush(LeGraphicsPalette::Background, Solarized::backgroundHighlight);
    palette.setBrush(LeGraphicsPalette::Grid, Solarized::secondaryContent);
    palette.setBrush(LeGraphicsPalette::Origin, Solarized::foreground);
    palette.setBrush(LeGraphicsPalette::Cursor, Solarized::foreground);
    palette.setBrush(LeGraphicsPalette::Selection, Solarized::foregroundHighlight);
    palette.setBrush(LeGraphicsPalette::Highlight, Solarized::foreground);
    palette.setBrush(LeGraphicsPalette::RubberBand, Solarized::foregroundHighlight);
    m_graphicsScene->setGraphicsPalette(palette);

    auto featureOption = LeGraphicsFeatureOption();
    featureOption.setAllFeatureVisible(true);
    featureOption.setAllFeatureBrush(QBrush(Solarized::foreground, Qt::SolidPattern));
    m_graphicsScene->foregroundLayer()->setFeatureOption(featureOption);
    m_graphicsScene->foregroundLayer()->setVisible(true);
}

QWidget *LayerViewer::centralWidget() const
{
    return m_graphicsView;
}

void LayerViewer::enableAntiAlias(bool enabled)
{
    m_graphicsView->enableAntiAliasing(enabled);
}

void LayerViewer::enableTransparentLayers(bool enabled)
{
    m_graphicsScene->enableTransparentLayers(enabled);
    // FIXME: m_layerListWidget->enableTransparentLayers(enabled);
}

void LayerViewer::enableDropShadowEffect(bool enabled)
{
    m_graphicsScene->enableDropShadowEffect(enabled);
}

void LayerViewer::enableOpenGL(bool enabled)
{
    m_graphicsView->enableOpenGL(enabled);
}

void LayerViewer::setLevelOfDetails(LevelOfDetails lod)
{
    switch (lod)
    {
        case LevelOfDetails::High:
            m_graphicsScene->setMinimumRenderSize(0.0);
            break;
        case LevelOfDetails::Medium:
            m_graphicsScene->setMinimumRenderSize(1.0);
            break;
        case LevelOfDetails::Low:
            m_graphicsScene->setMinimumRenderSize(2.0);
            break;
    }
}

void LayerViewer::createActions()
{
    QList<QAction *> zoomActions;

    zoomActions << createAction("Zoom In", "z,i",
                                this, [this]() { m_graphicsView->zoomIn(); })
                << createAction("Zoom Out", "z,o",
                                this, [this]() { m_graphicsView->zoomOut(); })
                << createAction("Zoom All", "z,a",
                                this, [this]() { m_graphicsView->zoomAll(); });

    QList<QAction*> viewportActions;
    viewportActions << createAction("Full viewport update", "v,f",
                                    this, [this]() {
        m_graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
        m_graphicsScene->update();
    })
                    << createAction("Minimal viewport update", "v,m",
                                    this, [this]() {
        m_graphicsView->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);
        m_graphicsScene->update();
    })
                    << createAction("Smart viewport update", "v,s",
                                    this, [this]() {
        m_graphicsView->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
        m_graphicsScene->update();
    })
                    << createAction("BoundRect viewport update", "v,b",
                                    this, [this]() {
        m_graphicsView->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
        m_graphicsScene->update();
    });
    auto actionGroup = createActionGroup(viewportActions);
    actionGroup->actions().at(1)->setChecked(true);

    QAction *viewProfilingAction = createAction("Enable view profiling", "v,p",
                                                this, [this](bool checked) {
        m_graphicsView->enableProfiling(checked);
        m_graphicsScene->update();
    });
    viewProfilingAction->setCheckable(true);

    QList<QAction*> viewInteractionActions;
    viewInteractionActions << createAction("Select mode", "s",
                                           this, [this]() {
        m_graphicsView->setInteractionMode(LeGraphicsView::MultipleSelectionMode);
    })
                           << createAction("Zoom mode", "z,b",
                                           this, [this]() {
        m_graphicsView->setInteractionMode(LeGraphicsView::ZoomMode);
    })
                           << createAction("Hand-scroll mode", "h",
                                           this, [this]() {
        m_graphicsView->setInteractionMode(LeGraphicsView::HandScrollMode);
    });
    actionGroup = createActionGroup(viewInteractionActions);
    actionGroup->actions().at(1)->setChecked(true);


    m_graphicsView->addActions(viewportActions);
    m_graphicsView->addActions(zoomActions);
    m_graphicsView->addAction(viewProfilingAction);
    m_graphicsView->addActions(viewInteractionActions);

    // exclusive
    // "zoom-box"
    // "pan"
    // "select"

    // exclusive, only in select mode
    // "Highlight hovered item"
    // "Show hovered item tooltip"
}

template <typename Func>
QAction *LayerViewer::createAction(const QString &text, const QString &shortcut,
                                   const QObject *receiver, Func slot)
{
    QAction *action = new QAction(text, this);
    if (!shortcut.isEmpty())
    {
        action->setShortcut(QKeySequence(shortcut));
        action->setToolTip(QString("%1 <i>%2</i>").arg(text).arg(shortcut));
    }

    connect(action, &QAction::triggered,
            receiver, slot);
    return action;
}

QActionGroup *LayerViewer::createActionGroup(const QList<QAction *> &actions)
{
    QActionGroup *group = new QActionGroup(this);
    group->setExclusive(true);
    for (auto action: actions)
    {
        action->setCheckable(true);
        action->setChecked(false);
        group->addAction(action);
    }
    return group;
}

void LayerViewer::setupTableViewDockWidget(const QString &title, QTableView *view, QDockWidget *dock)
{
    QWidget *widget = new QWidget;
    QLineEdit *filterEdit = new QLineEdit;
    QtTreePropertyBrowser *browser = new QtTreePropertyBrowser;
    QSplitter *splitter = new QSplitter(Qt::Vertical);
    widget->setLayout(new QVBoxLayout);
    widget->layout()->addWidget(filterEdit);
    widget->layout()->addWidget(splitter);
    splitter->addWidget(view);
    splitter->addWidget(browser);
    //QWidget *previousWidget = dock->widget();
    dock->setWidget(widget);
    //delete previousWidget;

    dock->setWindowTitle(title);
    dock->setObjectName(QString("%1DockWidget").arg(title));

    MultiColumnFilterProxyModel *filterProxy = new MultiColumnFilterProxyModel(this);
    filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
    filterProxy->setFilterKeyColumns(0, 20); // FIXME
    filterEdit->setPlaceholderText("filter...");
    filterEdit->setClearButtonEnabled(true);
    connect(filterEdit, &QLineEdit::textChanged,
            filterProxy, &QSortFilterProxyModel::setFilterWildcard);

    QSortFilterProxyModel *sortProxy = new QSortFilterProxyModel(this);
    filterProxy->setSourceModel(sortProxy);
    view->setModel(filterProxy);
    view->setSortingEnabled(true);

    view->setObjectName(QString("%1View").arg(title));
    view->setShowGrid(true);
    view->setAlternatingRowColors(true);
    view->setSelectionBehavior(QTableView::SelectRows);
    view->setSelectionMode(QTableView::SingleSelection);

    auto header = view->horizontalHeader();
    header->setSortIndicatorShown(true);
    header->setSectionsClickable(true);
    header->setSectionsMovable(true);
    header->setStretchLastSection(true);
    header->setSectionResizeMode(QHeaderView::Interactive);
    header->setDefaultSectionSize(100);
    header->setMinimumSectionSize(25);
    header->setMaximumSectionSize(300);

    header->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(header, &QHeaderView::customContextMenuRequested,
            [view](const QPoint &pos) {
        QMenu menu;
        QList<QAction*> actions;
        for (int column = 0; column < view->model()->columnCount(); column++)
        {
            const QString text = view->model()->headerData(column, Qt::Horizontal).toString();
            QAction *action = new QAction(text);
            action->setCheckable(true);
            action->setChecked(!view->isColumnHidden(column));
            actions.append(action);
            connect(action, &QAction::triggered,
                    [view, column](bool checked) {
                if (checked)
                    view->showColumn(column);
                else
                    view->hideColumn(column);
            });
        }
        menu.addActions(actions);
        menu.exec(view->horizontalHeader()->mapToGlobal(pos));
    });

    ItemModelPropertyBrowserAdaptor *adaptor = new ItemModelPropertyBrowserAdaptor(widget);
    adaptor->setItemModel(view->model());
    adaptor->setPropertyBrowser(browser);
    adaptor->setReadOnly(true);
    connect(view->selectionModel(), &QItemSelectionModel::currentRowChanged,
            adaptor, &ItemModelPropertyBrowserAdaptor::setModelIndex);
}

void LayerViewer::setupTableView(QTableView *view, QAbstractItemModel *model)
{
    QAbstractProxyModel *proxyModel;
    for (proxyModel = qobject_cast<QAbstractProxyModel*>(view->model());
         proxyModel != nullptr && qobject_cast<QAbstractProxyModel*>(proxyModel->sourceModel()) != nullptr;
         proxyModel = qobject_cast<QAbstractProxyModel*>(proxyModel->sourceModel()))
    {}
    if (proxyModel == nullptr)
        view->setModel(model);
    else
        proxyModel->setSourceModel(model);

    for (int column = 0; column < model->columnCount(); column++)
        if (column < 2)
            view->showColumn(column);
        else
            view->hideColumn(column);
}

void LayerViewer::setupTreeViewDockWidget(const QString &title, QTreeView *view, QDockWidget *dock)
{
    QWidget *widget = new QWidget;
    QLineEdit *filterEdit = new QLineEdit;
    QtTreePropertyBrowser *browser = new QtTreePropertyBrowser;
    QSplitter *splitter = new QSplitter(Qt::Vertical);
    widget->setLayout(new QVBoxLayout);
    widget->layout()->addWidget(filterEdit);
    widget->layout()->addWidget(splitter);
    splitter->addWidget(view);
    splitter->addWidget(browser);
    //QWidget *previousWidget = dock->widget();
    dock->setWidget(widget);
    //delete previousWidget;

    dock->setWindowTitle(title);
    dock->setObjectName(QString("%1DockWidget").arg(title));

    ItemModelPropertyBrowserAdaptor *adaptor = new ItemModelPropertyBrowserAdaptor(widget);
    adaptor->setItemModel(view->model());
    adaptor->setPropertyBrowser(browser);
    adaptor->setReadOnly(true);
    connect(view->selectionModel(), &QItemSelectionModel::currentRowChanged,
            adaptor, &ItemModelPropertyBrowserAdaptor::setModelIndex);
}

void LayerViewer::setupTreeView(QTreeView *view, QAbstractItemModel *model)
{
    view->setModel(model);
}

void LayerViewer::saveItemViewState(QAbstractItemView *view, QSettings &settings)
{
    settings.beginGroup(view->objectName());
    settings.setValue("geometry", view->saveGeometry());
//    settings.setValue("headerGeometry", view->horizontalHeader()->saveGeometry());
//    settings.setValue("headerState", view->horizontalHeader()->saveState());
    settings.endGroup();
}

void LayerViewer::restoreItemViewState(QAbstractItemView *view, QSettings &settings)
{
    settings.beginGroup(view->objectName());
    if (settings.allKeys().contains("geometry"))
        view->restoreGeometry(settings.value("geometry").toByteArray());
//    if (settings.allKeys().contains("headerGeometry"))
//        view->horizontalHeader()->restoreGeometry(settings.value("headerGeometry").toByteArray());
//    if (settings.allKeys().contains("headerState"))
//        view->horizontalHeader()->restoreState(settings.value("headerState").toByteArray());
    settings.endGroup();
}
